<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>cljs-web开发组件和知识点</title>
<meta name="author" content="青岛红创-马海强"/>
<meta name="description" content=""/>
<meta name="keywords" content=""/>
<style type="text/css">
.underline { text-decoration: underline; }
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js/dist/reveal.css"/>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js/dist/theme/moon.css" id="theme"/>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js/lib/css/zenburn.css"/></head>
<body>
<div class="reveal">
<div class="slides">
<!-- <section id="sec-title-slide"><h1 class="title">cljs-web开发组件和知识点</h1><p class="subtitle"></p>
<h2 class="author">青岛红创-马海强</h2>
</section> -->
<section id="sec-table-of-contents"><div id="table-of-contents">
<h2>大纲</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#/slide-org81413cd">用公司模板创建一个项目</a></li>
<li><a href="#/slide-org02cc14e">知识(踩坑)点</a></li>
<li><a href="#/slide-org5f79f8f">进阶</a></li>
</ul>
</div>
</div>
</section>

<section>
<section id="slide-org81413cd">
<h2 id="org81413cd">用公司模板创建一个项目</h2>
<aside class="notes">
<p>
Enter speaker notes here.
</p>

</aside>
</section>
</section>
<section>
<section id="slide-org84b6afe">
<h3 id="org84b6afe">新建项目</h3>
<div class="org-src-container">

<pre   ><code class="shell" >lein new hc-template cljs-management
</code></pre>
</div>
</section>
</section>
<section>
<section id="slide-org1d962c5">
<h3 id="org1d962c5">运行项目</h3>
<div class="outline-text-3" id="text-org1d962c5">
</div>
</section>
<section id="slide-org77b83c0">
<h4 id="org77b83c0">后端</h4>
<ul>
<li><p>
命令行启动
</p>
<div class="org-src-container">

<pre   ><code class="shell" >lein repl
</code></pre>
</div></li>
<li><p>
使用emacs+cider启动clj,
</p>

<p>
M-x cider-jack-in-clj
</p></li>

</ul>
</section>
<section id="slide-org24cb73c">
<h4 id="org24cb73c">前端</h4>
<ul>
<li><p>
命令行启动
</p>
<div class="org-src-container">

<pre   ><code class="shell" >npm install
</code></pre>
</div>

<div class="org-src-container">

<pre   ><code class="shell" >shadow-cljs server
</code></pre>
</div></li>
<li><p>
使用emacs+cider启动cljs,
</p>

<p>
M-x cider-jack-in-cljs,选择shadow
</p></li>

</ul>
</section>
</section>
<section>
<section id="slide-orgfb6a905">
<h3 id="orgfb6a905">clojurescript项目构成</h3>
<div class="outline-text-3" id="text-orgfb6a905">
</div>
</section>
<section id="slide-orgcbe3533">
<h4 id="orgcbe3533">依赖管理</h4>
<ul>
<li>shadow-cljs.edn
管理clojars依赖</li>
<li>package.json
管理npm的依赖包</li>

</ul>
</section>
<section id="slide-org0975823">
<h4 id="org0975823">前端build配置</h4>
<ul>
<li>shadow-cljs.edn</li>

</ul>
<div class="org-src-container">

<pre   ><code class="clojurescript" >:source-paths ["src/cljc" "src/cljs" "env/dev/cljs"]
:builds
 {:app
{:target :browser
 :output-dir "target/cljsbuild/public/dev/js"
 :asset-path "/js"
 :modules {:app {:entries [cljs-management.app]}}
 :devtools {:watch-dir "resources/public"
            :loader-mode :eval
            :preloads [re-frisk.preload]
            :console-support true}
 :closure-defines {"re_frame.trace.trace_enabled_QMARK_" true}
 :dev
 {:closure-defines {cljs-management.config/domain "http://localhost:3000"
                    cljs-management.common.utils/print-log? true}}}}
</code></pre>
</div>
</section>
<section id="slide-orgd42589e">
<h4 id="orgd42589e">前端环境变量</h4>
<ul>
<li>shadow-cljs.edn指定打包时环境</li>
<li><p>
通过修改goog-define定义参数名赋值
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(goog-define domain "http://localhost:3000")
</code></pre>
</div></li>

</ul>
</section>
<section id="slide-org3af4776">
<h4 id="org3af4776">Cljs启动流程</h4>
<ul>
<li><p>
shadow-cljs.edn配置文件指定环境里app的entries的namespace为: xxx.app
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(core/init! true)
</code></pre>
</div></li>
<li>core.cljs里的init!入口函数</li>
<li>kf/start!</li>

</ul>
</section>
<section id="slide-org129b535">
<h4 id="org129b535">调用顺序图</h4>

<div id="org51491b9" class="figure">
<p><img src="cljs-start.png" alt="cljs-start.png" />
</p>
</div>


</section>
</section>
<section>
<section id="slide-org02cc14e">
<h2 id="org02cc14e">知识(踩坑)点</h2>
<div class="outline-text-2" id="text-org02cc14e">
</div>
</section>
</section>
<section>
<section id="slide-org2d40702">
<h3 id="org2d40702">新建一个页面的要素</h3>
<div class="outline-text-3" id="text-org2d40702">
</div>
</section>
<section id="slide-orgc45d589">
<h4 id="orgc45d589">Route demo</h4>
<div class="org-src-container">

<pre   ><code class="clojure" >(def routes
 (reagent.core/atom
   [["" {:name  :home
         :title "首页"
         :icon  "home"
         :page  index-page}]
    ["/index" {:name :index :title "首页管理" :icon "home"}]
    ["/system" {:name :system :title "系统管理" :icon "setting"}
     ["/user" {:name  :system-user
               :title "系统用户"
               :page  sys-user-page}]
     ["/menu" {:name  :system-menu
               :title "系统菜单"
               :page  sys-menu-page}]]]))
</code></pre>
</div>
<p>
扩展
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(require '[reagent.core :as r])

(def num (r/atom 1))

(swap! num inc) ; =&gt; (inc num) =&gt; num = 2
(reset! num 5)  ; =&gt; num = 5
</code></pre>
</div>
</section>
<section id="slide-orgca853ea">
<h4 id="orgca853ea">路由使用</h4>
<dl>
<dt>路由用法</dt><dd>路径逐级拼接，数据做递归合并</dd>
<dt>(no term)</dt><dd>reitit.core/route &amp; ring-handler &amp; kee-frame.core/switch-route
<dl>
<dt>kee-frame.core/switch-route</dt><dd>简洁</dd>
<dt>reitit.core/route</dt><dd>创建一个路由</dd>
<dt>ring-handler</dt><dd>ring server的服务端路由</dd>

</dl></dd>

</dl>
</section>
<section id="slide-orga2bec0f">
<h4 id="orga2bec0f">Html入口 :: 讲解blog的"REAGENT 深入学习"三部分</h4>
<ul>
<li>form1:返回hiccup vector的函数
<a href="http://blog.3vyd.com/blog/posts-output/2019-07-20-reagent01/">reagent深入学习第一部分</a></li>
<li>form2:返回函数的函数
<a href="http://blog.3vyd.com/blog/posts-output/2019-07-21-reagent02/">reagent深入学习第二部分</a></li>
<li>form3:返回Class的函数
<a href="http://blog.3vyd.com/blog/posts-output/2019-07-21-reagent03/">reagent深入学习第三部分</a></li>

</ul>
</section>
</section>
<section>
<section id="slide-org91c6b73">
<h3 id="org91c6b73">db和他的服务员</h3>
<div class="outline-text-3" id="text-org91c6b73">
</div>
</section>
<section id="slide-orgdf70f45">
<h4 id="orgdf70f45">初始化db</h4>
<div class="org-src-container">

<pre   ><code class="cloure" >(kf/start! {:initial-db  nil})
</code></pre>
</div>
</section>
<section id="slide-org8506cc0">
<h4 id="org8506cc0">re-frame &amp; kee-frame</h4>
<div class="outline-text-4" id="text-org8506cc0">
</div>
<ul class="org-ul">
<li><a id="org13c011a"></a>re-frame<br />
<pre  class="example" >
lein new re-frame abcd +10x
</pre>
</li>
<li><a id="org3805182"></a>渊源 :: re-frame with batteries included.<br />
<ul>
<li>re-frame
是状态管理的基础,为前端提供了中心化数据存储和状态管理.</li>
<li>kee-frame
在re-frame的基础上做了一些扩展，把状态控制交给路由来做,
增加了controller(后面讲解)</li>

</ul>
</li>
<li><a id="orgf16c576"></a>使用<br />
<p>
在项目中是混合使用的.规则以现有代码为例.
</p>
</li>
</ul>
</section>
<section id="slide-orgc253cba">
<h4 id="orgc253cba">re-frame的reg-event-db \ reg-event-fx \ reg-event-ctx</h4>
<ul>
<li>reg-event-db
是reg-event-fx 的更专注、更有限的版本.当要处理程序只关心db值时,
入参和出参都是db</li>

<li>reg-event-fx
注册事件处理程序的最常见情况
入参:coeffects
出参:effects</li>

<li><p>
reg-event-ctx
更低级别,接收整个context,极少使用
</p>
<div class="org-src-container">

<pre   ><code class="clojure" >{:coeffects {:event [:some-id :some-param]
            :db    &lt;original contents of app-db&gt;}

:effects   {:db    &lt;new value for app-db&gt;
            :dispatch  [:an-event-id :param1]}

:queue     &lt;a collection of further interceptors&gt;
:stack     &lt;a collection of interceptors already walked&gt;}
</code></pre>
</div></li>

</ul>
</section>
<section id="slide-org8165407">
<h4 id="org8165407">kee-frame.core/controller</h4>
<ul>
<li>controller在发生路由跳转的时候触发.</li>
<li>controller是个map,里面的两个key,:params和:start分别挂了一个函数.
<ul>
<li>params:参数是路由
<ul>
<li>格式: [:路由名称 {路由参数map}]</li>

</ul></li>
<li>start:以context和第一个函数的返回值为输入或是nil.
<ul>
<li>格式: [ctx params的返回值]</li>

</ul></li>

</ul></li>
<li><p>
Example
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(kf/reg-controller
       ::product-form-controller
       {:params (fn [route]
                  (let [path (get-in route [:path-params :path])
                        query (get route :query-string)]
                    (when (and (= path "/index/qianggou-time/product-detail")
                               (re-find #"id=\w+" (or query "")))
                      (second (string/split query "=")))))
        :start (fn [_ id]
                 (rf/dispatch [::fetch-time-product-detail id]))})
</code></pre>
</div></li>

</ul>
</section>
<section id="slide-orgddbe72b">
<h4 id="orgddbe72b">start对应的函数是否执行由以下规则决定：</h4>
<ol>
<li>路由参数和返回值都和上次一样，start函数不执行</li>
<li>上次是nil，这次不是，start函数执行</li>
<li>上次不是nil这次是nil，执行:stop函数, :stop函数是可选的，不一定有</li>
<li>上次和本次都不是nil,且两次结果不一样,  先stop，再start</li>
<li>延伸
根据以上规则，常见场景
<ul>
<li><p>
启动时候仅仅执行一次的操作
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >{:params (constantly true) ;; true, or whatever non-nil value you prefer
 :start  [:call-me-once-then-never-again]}
</code></pre>
</div></li>
<li><p>
每次发生路由的时候都做一次的操作，比如logging
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >{:params identity
 :start  [:log-user-activity]}
</code></pre>
</div></li>
<li>最常见：到特定路由的时候发生的操作，比如请求某个接口</li>

</ul></li>

</ol>

</section>
</section>
<section>
<section id="slide-org7d59ef3">
<h3 id="org7d59ef3">跳转页面</h3>
<ul>
<li><p>
path-for
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(kee-frame.core/path-for [:todos {:id 14}]) =&gt; "/todos/14"
</code></pre>
</div></li>

<li><p>
navigate-to
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(reg-event-fx
 :todo-added
 (fn [_ [todo]]
   {:db          (update db :todos conj todo)
    :navigate-to [:todo :id (:id todo)]]}) ;; "/todos/14"
</code></pre>
</div></li>

</ul>
</section>
</section>
<section>
<section id="slide-org8b94419">
<h3 id="org8b94419">ajax组件 :: http-xhrio</h3>
<pre  class="example" >
(require '[day8.re-frame.http-fx])
(require '[ajax.core :as ajax])
</pre>
<ul>
<li><p>
json请求
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(re-frame/reg-event-fx
 ::http-post
 (fn [{:keys [db]} _]
   {:db (assoc db :show-loading true)
    :http-xhrio {:method          :post
                 :uri             "https://httpbin.org/post"
                 :params          data
                 :timeout         5000
                 :format          (ajax/json-request-format)
                 :response-format (ajax/json-response-format {:keywords? true})
                 :on-success      [::success-post-result]
                 :on-failure      [::failure-post-result]}}))
</code></pre>
</div></li>

<li><p>
form请求
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(re-frame/reg-event-fx
  ::http-form
  (fn [_world [_ val]]
    {:http-xhrio {:method :post
                     :uri "https://localhost:3000"
                     :body form-data
                     :headers {:authorization (get-token)}
                     :timeout 30000
                     :response-format (http/json-response-format {:keywords? true})
                     :on-failure [::error request-event nil]}}))

</code></pre>
</div></li>

</ul>
</section>
</section>
<section>
<section id="slide-org9f75310">
<h3 id="org9f75310">对标后台 :: clj-client</h3>
<div class="org-src-container">

<pre   ><code class="clojure" >(require '[clj-http.client :as http])
(http/get "http://cdn.imgs.3vyd.com/xh/admin/test.json" {:as :json})
</code></pre>
</div>

<div class="org-src-container">

<pre   ><code class="clojure" >(http/get "http://cdn.imgs.3vyd.com/xh/admin/test.json" {:as :json})
</code></pre>
</div>


<div class="org-src-container">

<pre   ><code class="clojure" >(http/post "http://localhost:8185/management/public/userList"
                                     {:form-params
                                      {:mobile   "15092107090"
                                       :nickName "marvin.ma"
                                       :name     "marvin"
                                       :openid   "8"}
                                      :content-type :json})
</code></pre>
</div>

<div class="org-src-container">

<pre   ><code class="clojure" >(http/post "http://localhost:8185/management/oauth2/token"
                                     {:form-params
                                      {:username   "test"
                                       :password   "test123"
                                       :client_id  "management-Client"
                                       :grant_type "password"}})
</code></pre>
</div>

</section>
</section>
<section>
<section id="slide-orgedc4905">
<h3 id="orgedc4905">基础知识</h3>
<div class="outline-text-3" id="text-orgedc4905">
</div>
</section>
<section id="slide-org925fb94">
<h4 id="org925fb94">reagent</h4>
</section>
<section id="slide-org8a74988">
<h4 id="org8a74988">hiccup</h4>
</section>
<section id="slide-org14bd242">
<h4 id="org14bd242">clojurescript</h4>


</section>
</section>
<section>
<section id="slide-org5f79f8f">
<h2 id="org5f79f8f">进阶</h2>
<div class="outline-text-2" id="text-org5f79f8f">
</div>
</section>
</section>
<section>
<section id="slide-org03f6039">
<h3 id="org03f6039">kee-frame.core/reg-chain</h3>
<ul>
<li><p>
example
</p>
<div class="org-src-container">

<pre   ><code class="clojurescript" >(kee-frame.core/reg-chain
 :league/load
             (fn [ctx [id]]
               {:http-xhrio {:method          :get
                             :uri             (str "/leagues/" id)}})

             (fn [{:keys [db]} [_ league-data]]
               {:db (assoc db :league league-data)
                :http-xhrio {:method          :get
                             :uri             (str "/leagues/" id)}})

             (fn [{:keys [db]} [_ league-data data2]]
               {:db (assoc db :league league-data)}))
</code></pre>
</div></li>
<li>第一个参数是dispatch时的参数，往后每个函数的第二组参数依次是每一次请求的结果。</li>
<li>应用
使用reg-chain解决token失效自动刷新问题,请求前先判断token是否过期,如果过期获取 个新token</li>

</ul>
</section>
<section id="slide-org5d57663">
<h4 id="org5d57663"><a href="http://blog.3vyd.com/blog/posts-output/2021-06-03-reagent-use-react-hook/">reagent使用react hook</a></h4>
</section>
<section id="slide-org43fbefe">
<h4 id="org43fbefe"><a href="http://blog.3vyd.com/blog/posts-output/2019-08-28-reagent-react/">React和Reagent交互</a></h4>
</section>
<section id="slide-orgef14de3">
<h4 id="orgef14de3">clojurescript和javascript交互</h4>
</section>
</section>
<section>
<section id="slide-orgd286794">
<h3 id="orgd286794">shadow-cljs打包不同环境</h3>
<div class="outline-text-3" id="text-orgd286794">
</div>
</section>
<section id="slide-org9ec8920">
<h4 id="org9ec8920">环境变量</h4>
</section>
</section>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/reveal.js/dist/reveal.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js/plugin/highlight/highlight.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js/plugin/notes/notes.js"></script>


<script>
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
plugins: [RevealHighlight, RevealNotes],
slideNumber:true
});

</script>
</body>
</html>
